from datetime import datetime, timedelta
from decimal import Decimal

from django.core.mail import EmailMultiAlternatives
from django.db.models import Q, F, Sum
from django.utils import timezone
from rest_framework.exceptions import ValidationError

from apps.booking.helpers.create_booking import get_tiers
from apps.booking.models import Booking, Appointment, AppointmentClientServices, BookingPrice, increment_booking_number
from apps.booking.types import BookingStatusChoices
from apps.booking.validations.validate_booking import count_valid_provider_in_other_booking_repeat, \
    count_valid_provider_in_other_appointment_repeat
from apps.location.models import RoomPrice, Room
from django.conf import settings


def validate_update_booking_api(instance, data):
    ONE_HOUR_SECONDS = 3600
    FIFTY_MINUTE_SECONDS = 900

    date = data.get('start_date', instance.start_date)
    start_time = instance.start_time
    end_time = instance.end_time

    if 'start_time' in data:
        start_time = data.get('start_time')
        start_time = datetime.strptime(start_time, '%H:%M:%S').time()
    if 'end_time' in data:
        end_time = data.get('end_time')
        end_time = datetime.strptime(end_time, '%H:%M:%S').time()

    if 'start_date' in data:
        date = datetime.fromtimestamp(date)
        data['start_date'] = date

    start_datetime = timezone.make_aware((datetime.combine(date.date(), start_time)),
        timezone.get_current_timezone())
    end_datetime = timezone.make_aware((datetime.combine(date.date(), end_time)),
                                       timezone.get_current_timezone())

    duration = (end_datetime - start_datetime).total_seconds()

    if start_time >= end_time:
        raise ValidationError({"msg": "Start Time must be lower than end Time"})
    if start_datetime < timezone.now():
        raise ValidationError({"msg": "Can not book in the past"})
    thirty_days = timezone.now() + timedelta(days=60)
    if start_datetime >= thirty_days:
        raise ValidationError({"msg": "Start Time must be less than Sixty days"})

    if duration < ONE_HOUR_SECONDS:
        raise ValidationError({"msg": "Duration must be at least one hour"})
    if duration % FIFTY_MINUTE_SECONDS != 0:
        raise ValidationError({"msg": "Booking duration must be 15-minutes increment"})

    invalid_provider_booking = count_valid_provider_in_other_booking_edit(
        instance, date, start_time, end_time)

    if invalid_provider_booking > 0:
        raise ValidationError(
            'This provider is in another booking at this time.')

    if 'room' in data:
        # room = Room.objects.
        # room = data['room']
        # data['location'] = room.location
        data['room_cost'] = handle_room_cost(instance)

    return data


def handle_room_cost(instance):
    prices = BookingPrice.objects.filter(booking=instance) \
        .annotate(total=(F('price') * F('hours')))
    room_cost = prices.aggregate(sum=Sum('total')).get('sum')
    room_cost = room_cost if room_cost is not None else Decimal(0.0)
    return room_cost


def count_valid_provider_in_other_booking_edit(
        instance, start_date, start_time, end_time):
    query = Booking.objects.all()
    query = query.exclude(id=instance.id)
    query = query.filter(
        start_date=start_date).exclude(
        status=BookingStatusChoices.CANCELED.value
    )
    query = query.filter(
        Q(start_time__range=(start_time, end_time)) |
        Q(end_time__range=(start_time, end_time))
    )
    query = query.exclude(
        Q(end_time=start_time) |
        Q(start_time=end_time)
    )

    return query.count()


def handle_appointment_on_booking_update(user, instance, data):
    appointment = Appointment.objects.filter(booking=instance.id).first()

    if appointment:

        if 'start_date' in data:
            start_date = data.get('start_date')
            appointment.start_date = start_date
            appointment.save()

        if 'selected_dates' in data:
            handle_booking_repeating_select_dates(user, appointment, data['selected_dates'])

    return instance


def date_diff(list1, list2):
    missing_elements = [x for x in list1 if x not in list2]
    return missing_elements


def handle_booking_repeating_select_dates(provider, instance, selected_dates=None):
    booking_instance = instance.booking
    if booking_instance.select_dates:
        parent_booking = booking_instance.is_recurrent_of if booking_instance.is_recurrent_of else booking_instance
        booking_selected_dates = [date.date() for date in parent_booking.selected_dates]
        booking_selected_dates.append(parent_booking.start_date.date())
        repeating_dates = [datetime.fromtimestamp(date).date() for date in selected_dates]
        new_added_dates = date_diff(repeating_dates, booking_selected_dates)
        if count_valid_provider_in_other_appointment_repeat(provider.id, new_added_dates,
                                                            booking_instance.start_time,
                                                            booking_instance.end_time) > 0:
            raise ValidationError(
                'This provider is in another booking in this time.')
        if count_valid_provider_in_other_booking_repeat(provider.id, new_added_dates,
                                                        booking_instance.start_time,
                                                        booking_instance.end_time) > 0:
            raise ValidationError(
                'This provider is in another booking in this time.')

        if len(new_added_dates) > 0:
            try:
                room_prices = None
                booking_instance = instance.booking
                parent = booking_instance if booking_instance.is_recurrent_of is None else booking_instance. \
                    is_recurrent_of
                appointments = []
                bookings = []

                start_datetime = timezone.make_aware(
                    (datetime.combine(booking_instance.start_date.date(), booking_instance.start_time)),
                    timezone.get_current_timezone())
                end_datetime = timezone.make_aware((datetime.combine(booking_instance.start_date.date(),
                                                                     booking_instance.end_time)),
                                                   timezone.get_current_timezone())
                duration = (end_datetime - start_datetime).total_seconds()

                selected_dates = new_added_dates
                for date in selected_dates:
                    booking = Booking(
                        start_date=date,
                        start_time=booking_instance.start_time,
                        end_time=booking_instance.end_time,
                        room=booking_instance.room,
                        location=booking_instance.location,
                        is_recurrent_of=parent,
                        booking_number=increment_booking_number(),
                        select_dates=True,
                        selected_dates=booking_instance.selected_dates,
                    )
                    booking_created = None
                    room = Room.objects.filter(id=booking.room).first()

                    if room.tiers_excluded is False:
                        tiers = get_tiers(provider, room.location, booking.start_date, duration)
                        total_price = Decimal(0.0)
                        if booking_instance.room is not None:
                            room_prices = list(RoomPrice.objects.filter(
                                room=room.id))
                            for room_price in room_prices:
                                for tier in tiers:
                                    if room_price.tier.tier == tier.get('tier'):
                                        total_price += room_price.cost * \
                                                       Decimal(tier.get('hours'))

                        room_cost = total_price
                        booking_dict = booking.__dict__
                        booking_dict.pop('_state', None)

                        booking_created = Booking.objects.create(**booking_dict)
                        if room_prices is not None:
                            for room_price in room_prices:
                                for tier in tiers:
                                    if room_price.tier.tier == tier.get('tier'):
                                        BookingPrice.objects.create(
                                            booking=booking_created,
                                            tier=tier['tier'],
                                            hours=tier['hours'],
                                            price=room_price.cost)
                    else:
                        booking_dict = booking.__dict__
                        booking_dict.pop('_state', None)

                        booking_created = Booking.objects.create(**booking_dict)
                        room_cost = Decimal(0.0)
                        hours = 0
                        for tier in tiers:
                            hours = hours + tier['hours']
                        BookingPrice.objects.create(
                            booking=booking,
                            hours=hours,
                            price=Decimal(0.0))
                    booking_created.room_cost = room_cost
                    booking_created.save()

                    bookings.append(booking_created)

                for booking in bookings:
                    appointment = Appointment(
                        booking=booking,
                        start_date=booking.start_date,
                        start_time=instance.start_time,
                        end_time=instance.end_time,
                        telehealth=instance.telehealth,
                        telehealth_zoom_id=None,
                        telehealth_google_id=None,
                    )
                    # if instance.telehealth is True:
                    #     if instance.telehealth_google_id:
                    #         appointment.telehealth_zoom_id = createZoomMeeting(
                    #             appointment.start_date,
                    #             appointment.start_time,
                    #             appointment.end_time)

                    appointments.append(appointment)

                appointments_created = Appointment.objects.bulk_create(appointments)

                appointment_services = instance.appointment_services.all()
                appointment_services = list(appointment_services)

                for appointment in appointments_created:
                    for client in instance.clients.all():
                        appointment.clients.add(client)
                        appointment.save()

                    for service in appointment_services:
                        AppointmentClientServices.objects.create(appointment=appointment, client=service.client,
                                                                 service=service.service, fees=service.fees)


            except Exception as e:
                print(e)
                from_email = settings.CONTACT_EMAIL
                subject = 'Sorry for the inconvenience. Your repeating appointments could not be created'

                msg = EmailMultiAlternatives(subject=subject, from_email=from_email, to=["ashir.rasheedx@gmail.com"])
                try:
                    msg.send()
                except Exception as ex:
                    print(ex)
